package com.lanyou.esb.service;

import java.io.IOException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.lanyou.esb.common.CommonUtils;
import com.lanyou.esb.common.Constants4Redis;
import com.lanyou.esb.datadictionary.RequestResultType;
import com.lanyou.esb.datadictionary.TimeUnit;
import com.lanyou.esb.redis.proxy.impl.QuantityAnalysisJedisProxy;
import com.lanyou.esb.redis.proxy.impl.ReqResTypeTotalTimesJedisProxy;
import com.lanyou.esb.util.DateUtils;
import com.lanyou.esb.vo.ServiceAnalysisVo;

/**
 * 流量分析服务
 * 
 * @author Davey.wu
 */
@Service
public class QuantityAnalysisService {
	@Autowired
	private QuantityAnalysisJedisProxy quantityAnalysisJedisProxy;
	@Autowired
	private ReqResTypeTotalTimesJedisProxy reqResTypeTotalTimesJedisProxy;

	/** 每分钟秒数 **/
	private static int SECONDS_OF_MINUTE = 60;
	/** 每小时分钟数 **/
	private static int MINUTE_OF_HOUR = 60;
	/** 每天小时数 **/
	private static int HOUR_OF_DAY = 24;

	/**
	 * 查找平台总量初始化信息
	 * 
	 * @param date
	 *            ：结束时间
	 * @param size
	 *            ：区间个数
	 * @return
	 * @throws JsonParseException
	 * @throws JsonMappingException
	 * @throws IOException
	 * @throws ParseException
	 */
	public ServiceAnalysisVo findQuantityAnalysisInfo(Date date, int size)
			throws JsonParseException, JsonMappingException, IOException,
			ParseException {

		// 获取时间区域列表
		List<String> timeSections = getKeys4QuanAnalysis(date, TimeUnit.SECOND,
				Constants4Redis.QUANTITY_ANALYSIS_INTERNAL_TIME, size);

		// redis数据查询键集合
		List<String> keys = new ArrayList<>(timeSections.size() * 3);
		// redis数据查询键组合
		List<Entry> entrys = new ArrayList<>(timeSections.size());
		for (String timeSection : timeSections) {
			Entry entryIn = new Entry();
			// 保存时间
			entryIn.setTime(timeSection);
			// 总量键值
			String key = quantityAnalysisJedisProxy.generateKey(timeSection);
			entryIn.setKey4Total(key);
			keys.add(key);
			// 成功数键值
			String keyS = quantityAnalysisJedisProxy.generateSfKey(timeSection,
					RequestResultType.SUCCEED.ValueCode);
			entryIn.setKey4S(keyS);
			keys.add(keyS);
			// 失败数键值
			String keyF = quantityAnalysisJedisProxy.generateSfKey(timeSection,
					RequestResultType.FAILURE.ValueCode);
			entryIn.setKey4F(keyF);
			keys.add(keyF);
			entrys.add(entryIn);
		}
		// 从REDIS中读取流量数据
		Map<String, Long> values = quantityAnalysisJedisProxy.get(keys);

		ServiceAnalysisVo result = new ServiceAnalysisVo();
		// ESB总流量
		List<ServiceAnalysisVo.Entry> totals = new ArrayList<>(size);
		// 请求成功次数
		List<ServiceAnalysisVo.Entry> failures = new ArrayList<>(size);
		// 请求失败次数
		List<ServiceAnalysisVo.Entry> success = new ArrayList<>(size);
		result.setFailures(failures);
		result.setSuccess(success);
		result.setTotals(totals);
		ServiceAnalysisVo.Entry entry = null;

		// 时间区间内当前平台总流量读数
		Long value4T = 0l;
		// 时间区间内当前成功次数读数
		Long value4S = 0l;
		// 时间区间内当前失败次数读数
		Long value4F = 0l;
		for (Entry value : entrys) {
			long time = DateUtils.DATEPATTERN_YYYYMMDDHHMMSS.parse(
					value.getTime()).getTime();
			// 读取总流量
			value4T = values.get(value.getKey4Total());
			if (value4T == null) {
				value4T = 0l;
			}
			entry = result.new Entry(time, value4T);
			totals.add(entry);

			// 读取成功次数
			value4S = values.get(value.getKey4S());
			if (value4S == null) {
				value4S = 0l;
			}
			entry = result.new Entry(time, value4S);
			success.add(entry);

			// 读取失败次数
			value4F = values.get(value.getKey4F());
			if (value4F == null) {
				value4F = 0l;
			}
			entry = result.new Entry(time, value4F);
			failures.add(entry);
		}
		return result;
	}

	/**
	 * 查找EBS总量最新信息
	 * 
	 * @param valueLTotal
	 *            ：区间内已读的总流量
	 * @param valueLS
	 *            ：区间内已读的成功数
	 * @param valueLF
	 *            ：区间内已读的失败数
	 * @param leftDate
	 *            ：上次读数的时间点
	 * @param rightDate
	 *            ：区间右侧时间点
	 * @return
	 * @throws JsonParseException
	 * @throws JsonMappingException
	 * @throws IOException
	 * @throws ParseException
	 */
	public Map<String, Object> getLatestQuantityInfo(long valueLTotal,
			long valueLS, long valueLF, Date leftDate, Date rightDate)
			throws JsonParseException, JsonMappingException, IOException,
			ParseException {
		valueLTotal = valueLTotal > 0 ? valueLTotal : 0;
		valueLS = valueLS > 0 ? valueLS : 0;
		valueLF = valueLF > 0 ? valueLF : 0;
		// 减去已读的数据
		long resultTotal = -valueLTotal;
		long resultS = -valueLS;
		long resultF = -valueLF;
		Map<String, Object> map = new HashMap<>(2);
		// 获取时间区域列表
		List<String> timeSections = getKeys4QuanAnalysis(leftDate, rightDate,
				TimeUnit.SECOND,
				Constants4Redis.QUANTITY_ANALYSIS_INTERNAL_TIME);
		// 按时间从小到大排序
		Collections.sort(timeSections);
		// redis数据查询键集合
		List<String> keys = new ArrayList<>(timeSections.size() * 3);
		// redis数据查询键组合
		List<Entry> entrys = new ArrayList<>(timeSections.size());
		for (String timeSection : timeSections) {
			Entry entry = new Entry();
			// 总量键值
			String key = quantityAnalysisJedisProxy.generateKey(timeSection);
			entry.setKey4Total(key);
			keys.add(key);
			// 成功数键值
			String keyS = quantityAnalysisJedisProxy.generateSfKey(timeSection,
					RequestResultType.SUCCEED.ValueCode);
			entry.setKey4S(keyS);
			keys.add(keyS);
			// 失败数键值
			String keyF = quantityAnalysisJedisProxy.generateSfKey(timeSection,
					RequestResultType.FAILURE.ValueCode);
			entry.setKey4F(keyF);
			keys.add(keyF);
			entrys.add(entry);
		}
		// 从REDIS中读取流量数据
		Map<String, Long> values = quantityAnalysisJedisProxy.get(keys);
		// 时间区间内当前平台总流量读数
		Long value4T = 0l;
		// 时间区间内当前成功次数读数
		Long value4S = 0l;
		// 时间区间内当前失败次数读数
		Long value4F = 0l;
		for (Entry entry : entrys) {
			// 读取总流量
			value4T = values.get(entry.getKey4Total());
			if (value4T == null) {
				value4T = 0l;
			}
			resultTotal += value4T;

			// 读取成功次数
			value4S = values.get(entry.getKey4S());
			if (value4S == null) {
				value4S = 0l;
			}
			resultS += value4S;

			// 读取失败次数
			value4F = values.get(entry.getKey4F());
			if (value4F == null) {
				value4F = 0l;
			}
			resultF += value4F;
		}

		if (resultTotal < 0) {
			resultTotal = 0;
		}
		if (resultS < 0) {
			resultS = 0;
		}
		if (resultF < 0) {
			resultF = 0;
		}

		// 上次读数的时间区间
		String lefSectionKey = getSectionKey(leftDate, TimeUnit.SECOND,
				Constants4Redis.QUANTITY_ANALYSIS_INTERNAL_TIME);
		// 上次读数与本次读数在同一时间区间内
		if (timeSections.get(timeSections.size() - 1).equals(lefSectionKey)) {
			// 设置已计数据为当前计数与前一次更新计数之和
			valueLTotal += resultTotal;
			valueLS += resultS;
			valueLF += resultF;
		} else {
			// 设置已计数据为最后一个区间的计数
			valueLTotal = value4T;
			valueLS = value4S;
			valueLF = value4F;
		}
		// 保存本次总流量读数
		map.put(CommonUtils.MSG_FIELD_VALUE_TOTAL, resultTotal);
		// 保存本次成功次数读数
		map.put(CommonUtils.MSG_FIELD_VALUE_S, resultS);
		// 保存本次失败次数读数
		map.put(CommonUtils.MSG_FIELD_VALUE_F, resultF);

		// 最后一个区间总流量已读次数
		map.put(CommonUtils.MSG_FIELD_LEFT_VALUE_4_TOTAL, valueLTotal);
		// 最后一个区间成功次数已读次数
		map.put(CommonUtils.MSG_FIELD_LEFT_VALUE_4_S, valueLS);
		// 最后一个区间失败次数已读次数
		map.put(CommonUtils.MSG_FIELD_LEFT_VALUE_4_F, valueLF);
		return map;
	}

	/**
	 * 获取时间区域的所有键值
	 * 
	 * @param lastDate
	 * @param now
	 * @param timeUnit
	 * @param interval
	 * @return
	 */
	private List<String> getKeys4QuanAnalysis(Date lastDate, Date now,
			TimeUnit timeUnit, int interval) {
		switch (timeUnit) {
		case SECOND:
			return getKeys(lastDate, now, interval, Calendar.SECOND,
					DateUtils.DATEPATTERN_YYYYMMDDHHMMSS);
		case MINUTE:
			return getKeys(lastDate, now, interval, Calendar.MINUTE,
					DateUtils.DATEPATTERN_YYYYMMDDHHMM);
		case HOUR:
			return getKeys(lastDate, now, interval, Calendar.HOUR_OF_DAY,
					DateUtils.DATEPATTERN_YYYYMMDDHH);
		case MONTH:
			// TODO 暂时不会到达这个维度
			break;
		case YEAR:
			// TODO 暂时不会到达这个维度
			break;
		default:
			return new ArrayList<>(0);
		}
		return new ArrayList<>(0);
	}

	/**
	 * 获取时间区域的所有键值
	 * 
	 * @param date
	 * @return
	 */
	public List<String> getKeys4QuanAnalysis(Date date, TimeUnit timeUnit,
			int interval, int size) {
		switch (timeUnit) {
		case SECOND:
			return getKeys(date, interval, size, Calendar.SECOND,
					DateUtils.DATEPATTERN_YYYYMMDDHHMMSS);
		case MINUTE:
			return getKeys(date, interval, size, Calendar.MINUTE,
					DateUtils.DATEPATTERN_YYYYMMDDHHMM);
		case HOUR:
			return getKeys(date, interval, size, Calendar.HOUR_OF_DAY,
					DateUtils.DATEPATTERN_YYYYMMDDHH);
		case MONTH:
			// TODO 暂时不会到达这个维度
			break;
		case YEAR:
			// TODO 暂时不会到达这个维度
			break;
		default:
			return new ArrayList<>(0);
		}
		return new ArrayList<>(0);
	}

	/**
	 * 获取时间间隔键值
	 * 
	 * @param date
	 *            ：日期
	 * @param interval
	 *            ：时间间隔
	 * @param size
	 *            ：区间个数
	 * @param field
	 *            ：时间域名
	 * @param simpleDateFormat
	 *            ：时间格式
	 * @return
	 */
	private List<String> getKeys(Date date, int interval, int size, int field,
			SimpleDateFormat simpleDateFormat) {
		Calendar calendar = Calendar.getInstance();
		calendar.setTime(date);
		int second = calendar.get(field);
		calendar.set(field, second + interval - (second % interval));
		List<String> result = new ArrayList<>(size);
		while (size-- > 0) {
			result.add(simpleDateFormat.format(calendar.getTime()));
			// 时间递减
			calendar.add(field, -interval);
		}
		return result;
	}

	/**
	 * 获取时间间隔键值
	 * 
	 * @param start
	 *            ：开始日期
	 * @param end
	 *            ：结束日期
	 * @param interval
	 *            ：时间间隔
	 * @param field
	 *            ：时间域名
	 * @param simpleDateFormat
	 *            ：时间格式
	 * @return
	 */
	private List<String> getKeys(Date start, Date end, int interval, int field,
			SimpleDateFormat simpleDateFormat) {
		Calendar calendarStart = Calendar.getInstance();
		calendarStart.setTime(start);
		calendarStart.add(field, interval
				- (calendarStart.get(field) % interval));

		Calendar calendarEnd = Calendar.getInstance();
		calendarEnd.setTime(end);
		calendarEnd.add(field, interval - (calendarEnd.get(field) % interval));

		List<String> result = new ArrayList<>(2);
		while (!calendarStart.after(calendarEnd)) {
			result.add(simpleDateFormat.format(calendarStart.getTime()));
			// 时间递增
			calendarStart.add(field, interval);
		}
		return result;
	}

	/**
	 * 获取时间区间的REDIS键值
	 * 
	 * @param date
	 *            :日期
	 * @param timeUnit
	 *            ：时间单位
	 * @param interval
	 *            ：时间间隔
	 * @return
	 */
	private String getSectionKey(Date date, TimeUnit timeUnit, int interval) {
		switch (timeUnit) {
		case SECOND:
			return secondsSection(date, interval);
		case MINUTE:
			return minuteSection(date, interval);
		case HOUR:
			return hourSection(date, interval);
		case MONTH:
			// TODO 暂时不会到达这个维度
			break;
		case YEAR:
			// TODO 暂时不会到达这个维度
			break;
		default:
			return "";
		}
		return "";
	}

	/**
	 * 以小时为单位，划分时间区间<br>
	 * 描述：时间间隔应该是24的公因数
	 * 
	 * @param date
	 *            :日期
	 * @param interval
	 *            ：时间间隔
	 * @return
	 */
	private String hourSection(Date date, int interval) {
		// 不是24的公因数，视为无效时间间隔
		if (HOUR_OF_DAY % interval != 0) {
			return "";
		}

		Calendar calendar = Calendar.getInstance();
		calendar.setTime(date);
		// 计算时所在区间的起始位置
		calendar.add(Calendar.HOUR_OF_DAY,
				-(calendar.get(Calendar.HOUR_OF_DAY) % interval));

		return DateUtils.DATEPATTERN_YYYYMMDDHH.format(calendar.getTime());
	}

	/**
	 * 以分钟为单位，划分时间区间<br>
	 * 描述：时间间隔应该是60的公因数
	 * 
	 * @param date
	 *            :日期
	 * @param interval
	 *            ：时间间隔
	 * @return
	 */
	private String minuteSection(Date date, int interval) {
		// 不是60的公因数，视为无效时间间隔
		if (MINUTE_OF_HOUR % interval != 0) {
			return "";
		}
		Calendar calendar = Calendar.getInstance();
		calendar.setTime(date);
		// 计算分所在区间的起始位置
		calendar.add(Calendar.MINUTE,
				-(calendar.get(Calendar.MINUTE) % interval));

		return DateUtils.DATEPATTERN_YYYYMMDDHHMM.format(calendar.getTime());
	}

	/**
	 * 以秒为单位，划分时间区间<br>
	 * 描述：时间间隔应该是60的公因数
	 * 
	 * @param date
	 *            :日期
	 * @param interval
	 *            ：时间间隔
	 * @return
	 */
	private String secondsSection(Date date, int interval) {
		// 不是60的公因数，视为无效时间间隔
		if (SECONDS_OF_MINUTE % interval != 0) {
			return "";
		}
		Calendar calendar = Calendar.getInstance();
		calendar.setTime(date);
		// 计算秒所在区间的起始位置
		calendar.add(Calendar.SECOND,
				-(calendar.get(Calendar.SECOND) % interval));

		return DateUtils.DATEPATTERN_YYYYMMDDHHMMSS.format(calendar.getTime());
	}

	/**
	 * 内部类，用于记录查询redis的键
	 * 
	 * @author Davey.wu
	 */
	private static class Entry {
		/** 时间 **/
		private String time;
		/** 平台总流量 **/
		private String key4Total;
		/** 成功次数 **/
		private String key4S;
		/** 失败次数 **/
		private String key4F;

		public String getKey4Total() {
			return key4Total;
		}

		public void setKey4Total(String key4Total) {
			this.key4Total = key4Total;
		}

		public String getKey4S() {
			return key4S;
		}

		public void setKey4S(String key4s) {
			key4S = key4s;
		}

		public String getKey4F() {
			return key4F;
		}

		public void setKey4F(String key4f) {
			key4F = key4f;
		}

		public String getTime() {
			return time;
		}

		public void setTime(String time) {
			this.time = time;
		}

	}
}
